﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using log4net;
using System.Threading;
using System.IO;
using System.Web.Script.Serialization;

namespace Repo7.BusinessLogic.Local
{
    /// <summary>
    /// Repository Info Manager => stores and updates repository info
    /// </summary>
    public class RepoInfoManager
    {
        #region Private Members

        /// <summary>
        /// Logger
        /// </summary>
        protected static readonly ILog logger = LogManager.GetLogger(typeof(RepoInfoManager));

        /// <summary>
        /// Reader Writer Lock
        /// </summary>
        private ReaderWriterLockSlim rwLock;

        /// <summary>
        /// Repository Directory
        /// </summary>
        private string repositoryDirPath;

        /// <summary>
        /// Repository Files => key = line number , value = repository file info
        /// </summary>
        private IList<RepoFileInfo> repositoryFiles;

        /// <summary>
        /// File path of the repository info file
        /// </summary>
        private string repositoryInfoFilePath;

        #endregion

        #region Public Properties

        /// <summary>
        /// Default repository information file name
        /// </summary>
        public const string repositoryInfoFileName = "repo.info";

        #endregion

        #region Constructors

        public RepoInfoManager(string dirPath)
        {
            //Configure log 4 net
            log4net.Config.XmlConfigurator.Configure();

            repositoryDirPath = dirPath;
            repositoryInfoFilePath = string.Format("{0}\\{1}", dirPath, repositoryInfoFileName);
            rwLock = new ReaderWriterLockSlim();
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Builds the File Info Repository
        /// </summary>
        public void InitRepositoryFilesInfo()
        {
            FileInfo repoInfo = new FileInfo(repositoryInfoFilePath);

            //If file exists read it, else create it
            if (repoInfo.Exists)
            {
                ReadRepositoryInfoFile(repoInfo.OpenRead());
            }
            else
            {
                CreateRepositoryInfoFile(repoInfo);
            }
        }

        /// <summary>
        /// Update the element which has the specified Id
        /// </summary>
        /// <param name="id">Repository File Id</param>
        /// <param name="newElement">New Data</param>
        public void UpdateFile(RepoFileInfo newElement)
        {
            var itemToUpdate = repositoryFiles.SingleOrDefault(x => x.Name == newElement.Name);
            if (itemToUpdate != null)
            {
                itemToUpdate.UpdateInfo(newElement);
            }
            WriteRepositoryFilesToRepoInfo();
        }

        /// <summary>
        /// Adds another element to file info
        /// </summary>
        /// <param name="id">Repository File Id</param>
        /// <param name="newElement">New Data</param>
        public void AddFile(RepoFileInfo newElement)
        {
            long? maxFileId = repositoryFiles.Select(x => x.Id).OrderByDescending(x => x).FirstOrDefault();
            if (!maxFileId.HasValue)
            {
                maxFileId = 0;
            }

            newElement.Id = maxFileId.Value + 1;
            repositoryFiles.Add(newElement);
            WriteRepositoryFilesToRepoInfo();
        }

        /// <summary>
        /// Deletes an element which has a specific name
        /// </summary>
        /// <param name="id"></param>
        public void DeleteFile(string fileName)
        {
            var enumerator = repositoryFiles.GetEnumerator();
            while (enumerator.MoveNext())
            {
                if (enumerator.Current.Name == fileName)
                {
                    enumerator.Current.IsDeleted = true;
                    break;
                }
            }
            WriteRepositoryFilesToRepoInfo();
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Creates a Repository Info File
        /// </summary>
        private void CreateRepositoryInfoFile(FileInfo newRepoInfoFile)
        {
            //If file exists throw error
            if (newRepoInfoFile.Exists)
            {
                throw new Exception(string.Format("File '{0}' already exists in directory '{1}'.", repositoryInfoFileName, repositoryDirPath));
            }

            //Create file
            newRepoInfoFile.Create();

            //Create the Repository Files from the physical files
            repositoryFiles = FileUtils.GetRepositoryFilesInDir(repositoryDirPath, new string[] { repositoryInfoFileName });

            WriteRepositoryFilesToRepoInfo();

        }

        /// <summary>
        /// Write the Repository File Collection to the repo.info file
        /// </summary>
        private void WriteRepositoryFilesToRepoInfo()
        {
            try
            {
                rwLock.EnterWriteLock();
                //Get file info 
                FileInfo fileInfo = new FileInfo(repositoryInfoFilePath);

                using (var fileStream = fileInfo.OpenWrite())
                {
                    WriteRepositoryFilesToRepoInfo(fileStream);
                }
            }
            catch (Exception exception)
            {
                logger.Error("Exception in writing to repo.info .", exception);
            }
            finally
            {
                rwLock.ExitWriteLock();
            }
        }

        /// <summary>
        /// Write the Repository File Collection to the repo.info file
        /// </summary>
        private void WriteRepositoryFilesToRepoInfo(FileStream fileStream)
        {
            //Populate file
            using (var writer = new StreamWriter(fileStream))
            {
                foreach (var fileInfo in repositoryFiles)
                {
                    writer.WriteLine(fileInfo.GetJson());
                }
            }
        }

        /// <summary>
        /// Reads Repository Info File
        /// </summary>
        private void ReadRepositoryInfoFile(FileStream repoInfoFileStream)
        {
            try
            {
                rwLock.EnterReadLock();
                repositoryFiles = new List<RepoFileInfo>();
                JavaScriptSerializer serializer = new JavaScriptSerializer();
                using (var reader = new StreamReader(repoInfoFileStream))
                {
                    string line;
                    while ((line = reader.ReadLine()) != null)
                    {
                        if (!string.IsNullOrEmpty(line))
                        {
                            RepoFileInfo newRepoFileInfoInfo = serializer.Deserialize(line, typeof(RepoFileInfo)) as RepoFileInfo;
                            repositoryFiles.Add(newRepoFileInfoInfo);
                        }
                    }
                }
            }
            catch (Exception exception)
            {
                logger.Error("Cannot read repo.info file.", exception);
            }
            finally
            {
                rwLock.ExitReadLock();
            }

        }


        #endregion
    }
}
